﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace NetFrameworkExtensions.BusinessLayer
{
  public class ItemUpdaterImpl :
    IItemUpdater
  {
    public IIdentified Create(object target, IIdentified source, object parentItemId = null)
    {
      if (target == null) throw new ArgumentNullException("target");
      if (source == null) throw new ArgumentNullException("source");
      return CreateInternal(target, source, parentItemId, null);
    }

    public bool Update(object target, IIdentified source)
    {
      if (target == null) throw new ArgumentNullException("target");
      if (source == null) throw new ArgumentNullException("source");
      return UpdateInternal(target, source);
    }

    public bool Remove(object target, IIdentified source)
    {
      if (target == null) throw new ArgumentNullException("target");
      if (source == null) throw new ArgumentNullException("source");
      return RemoveInternal(target, source);
    }

    private IIdentified CreateInternal(object target, IIdentified source, object parentItemId, object currentParentId)
    {
      var identified = target as IIdentified;
      var container = target as IItemContainer;
      var collection = target as IEnumerable;

      if (container != null && Object.Equals(parentItemId, currentParentId))
      {
        var result = container.CreateFrom(source);
        if (result != null) return result;
      }

      if (collection != null)
        foreach (var child in collection.OfType<object>().Where(o => o != null))
        {
          var result = CreateInternal(child, source, parentItemId, identified != null ? identified.Identifier : currentParentId);
          if (result != null) return result;
        }

      var childProps = IsContentPropertyAttribute.FindProperties(target.GetType());
      foreach (var prop in childProps)
      {
        var child = prop.GetValue(target, null);
        if (child != null)
        {
          var result = CreateInternal(child, source, parentItemId, identified != null ? identified.Identifier : currentParentId);
          if (result != null) return result;
        }
      }

      return null;
    }
    private bool UpdateInternal(object target, IIdentified source)
    {
      var identified = target as IIdentified;
      var container = target as IItemContainer;
      var collection = target as IEnumerable;
      var propertyContainer = target as IPropertyContainer;

      bool changed = false;

      if (identified != null && Object.Equals(identified.Identifier, source.Identifier) && propertyContainer != null)
      {
        propertyContainer.SetProperties(source);
        changed = true;
      }

      if (collection != null)
        foreach (var item in collection)
          changed = UpdateInternal(item, source) || changed;

      var childProps = IsContentPropertyAttribute.FindProperties(target.GetType());
      foreach (var prop in childProps)
      {
        var child = prop.GetValue(target, null);
        if (child != null)
          changed = UpdateInternal(child, source) || changed;
      }

      return changed;
    }
    private bool RemoveInternal(object target, IIdentified source)
    {
      var container = target as IItemContainer;
      var collection = target as IEnumerable;

      bool changed = false;

      if (/*collection != null && */container != null)
      {
        var result = container.RemoveFrom(source);
        changed = result != null || changed;
      }
      else
        if (collection != null)
          foreach (var item in collection)
            changed = RemoveInternal(item, source) || changed;

      var childProps = IsContentPropertyAttribute.FindProperties(target.GetType());
      foreach (var prop in childProps)
      {
        var child = prop.GetValue(target, null);
        if (child != null)
          changed = RemoveInternal(child, source) || changed;
      }

      return changed;
    }
  }
}
